package com.atguigu.gmall.gateway.filters;

import com.atguigu.gmall.common.utils.IpUtils;
import com.atguigu.gmall.common.utils.JwtUtils;
import com.atguigu.gmall.gateway.config.JwtProperties;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

@EnableConfigurationProperties(JwtProperties.class)
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.PathConfig> {

    public AuthGatewayFilterFactory() {
        super(PathConfig.class);
    }

    @Autowired
    JwtProperties jwtProperties;
    @Override
    public GatewayFilter apply(PathConfig config) {
        return (ServerWebExchange exchange, GatewayFilterChain chain)->{
            System.out.println("局部过滤器，只拦截配置了该过滤器的服务。paths="+config.paths);
            // 获取请求及响应对象 ServerHttpRequest --> HttpServletRequest
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            // 1.获取当前请求的路径，判断是否在拦截名单中（config.paths），不在直接放行
            String curPath = request.getURI().getPath();
            List<String> paths=  config.paths;
           // StringUtils.indexOf(curPath,path)<0 表示没有一个字母是匹配的
            if(paths.stream().allMatch(path-> StringUtils.indexOf(curPath,path)<0)){
                return chain.filter(exchange);
            }

            // 2.获取请求中的jwt：cookie（同步） 头信息（异步）
            //异步头中的token
            String token = request.getHeaders().getFirst("token");
            if(StringUtils.isBlank(token)){
                MultiValueMap<String, HttpCookie> cookies = request.getCookies();
                //判断cookie，cookie里面应该有key为gmall-token
                if(!CollectionUtils.isEmpty(cookies)&&cookies.containsKey(jwtProperties.getCookieName())){
                     token = cookies.getFirst(jwtProperties.getCookieName()).getValue();
                }
            }
            // 3.判断jwt类型的token是否为空，重定向到登录页面。
            if(StringUtils.isBlank(token)){
                response.setStatusCode(HttpStatus.SEE_OTHER);//403重定向状态码
                response.getHeaders().set(HttpHeaders.LOCATION,"http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());//地址
                return response.setComplete();//这样就返回401 了，否则就会继续往后面执行
            }
            try {
                // 4.尝试解析jwt，如果出现异常则重定向到登录页面
                Map<String, Object> map = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
                // 5.获取载荷中的ip地址 和 当前请求的ip地址 比较，不同则重定向到登录
                String ip = map.get("ip").toString();
                String curIp = IpUtils.getIpAddressAtGateway(request);
                if (!StringUtils.equals(ip,curIp)) {
                    response.setStatusCode(HttpStatus.SEE_OTHER);//403重定向状态码
                    response.getHeaders().set(HttpHeaders.LOCATION,"http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());//地址
                    return response.setComplete();//这样就返回401 了，否则就会继续往后面执行
                }
                // 6.把用户信息传递给后续服务（通过请求头信息把用户信息发送到后续微服务，因为解析jwt很耗时，在网关解析一次就够了）
                //mutate()设置新的覆盖老的
                request.mutate().header("userId",map.get("id").toString()).build();
                exchange.mutate().request(request).build();
                // 7.放行
                return chain.filter(exchange);
            } catch (Exception e) {
                e.printStackTrace();
                response.setStatusCode(HttpStatus.SEE_OTHER);//403重定向状态码
                response.getHeaders().set(HttpHeaders.LOCATION,"http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());//地址
                return response.setComplete();//这样就返回401 了，否则就会继续往后面执行
            }
        } ;
    }
    //-Auth=/xxx 接收pathConfig里的参数，"paths"一个字段
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("paths");
    }

    //接收集合字段
    // - Auth=/xxx,/yyy,/zzz 用集合字段一次性获取多个参数（xxx,yyy,zzz）
    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.GATHER_LIST;
    }

    @Data
    public static class PathConfig{
        private List<String> paths;
    }
}
